﻿//========================================================= 
/**@file WinComInstaller.h
 * @brief Windows下USB转串口安装器
 * 
 * @date 2016-09-15   10:01:54
 * @author Zhyioo 
 * @version 1.0
 */ 
//--------------------------------------------------------- 
#ifndef _LIBZHOUYB_WINCOMINSTALLER_H_
#define _LIBZHOUYB_WINCOMINSTALLER_H_
//--------------------------------------------------------- 
#include <cfgmgr32.h>

#include "SetupDiHelper.h"
#include "WinRegistryKey.h"
//--------------------------------------------------------- 
namespace zhou_yb {
namespace extension {
namespace ability {
//--------------------------------------------------------- 
/// 串口GUID
#define COM_GUID_VALUE { 0x86e0d1e0L, 0x8089, 0x11d0, 0x9c, 0xe4, 0x08, 0x00, 0x3e, 0x30, 0x1f, 0x73 }
//--------------------------------------------------------- 
/// 修改串口时的数据结构
struct ModifyComTAG
{
    uint OldPort;
    uint NewPort;
    string NewName;
};
//--------------------------------------------------------- 
/// Windows下串口设置工具
class WinComInstaller
{
public:
    //----------------------------------------------------- 
    WinComInstaller() {}
    //----------------------------------------------------- 
    /// 重置注册表中的串口属性
    static bool _ResetComRegCurrentSetEnum(const char* devInstanceID, const ByteArray& newName)
    {
        string currentCtlSetEnum = "HKEY_LOCAL_MACHINE\\System\\CurrentControlSet\\ENUM\\";
        currentCtlSetEnum += devInstanceID;
        RegistryKey key = RegistryKey::OpenKey(currentCtlSetEnum.c_str());
        // 权限不足
        if(!key.IsValid())
            return false;

        // 设置PortName
        RegistryKey subKey = key.OpenSubKey("Device Parameters", KEY_ALL_ACCESS);
        if(!subKey.IsValid())
            return false;

        RegValue portNameVal;
        const char portNameID[] = "PortName";
        if(!subKey.GetValue(portNameID, portNameVal))
            return false;

        portNameVal.Value = newName;
        if(!subKey.SetValue(portNameID, portNameVal))
            return false;

        return true;
    }
    /// 重置注册表中的串口序号
    static bool _ResetComRegSerialCOMM(const ByteArray& oldName, const ByteArray& newName)
    {
        RegistryKey key = RegistryKey::OpenKey("HKEY_LOCAL_MACHINE\\HARDWARE\\DEVICEMAP\\SERIALCOMM");
        if(!key.IsValid())
            return false;

        list_t<RegValueItem> subValue;
        key.EnumValue(subValue);

        list_t<RegValueItem>::iterator itr;
        for(itr = subValue.begin();itr != subValue.end(); ++itr)
        {
            ByteArray subName((*itr).Item.Value);
            if(StringConvert::Compare(subName, newName, true))
                return true;
            if(StringConvert::Compare(subName, oldName, true))
                break;
        }
        if(itr == subValue.end())
            return false;
        (*itr).Item.Value = newName;

        if(!key.SetValue(*itr))
            return false;
        return true;
    }
    /// 设置串口属性
    static bool _SetComProperty(HDEVINFO hDevInfo, PSP_DEVINFO_DATA pDevData, 
        const ByteArray& displayName, const ByteArray& oldName, 
        const ByteArray& newName, LoggerAdapter* plog = NULL)
    {
        LOGGER(LoggerAdapter _log;
        if(plog != NULL) _log.Select(*plog));
        // 禁用设备
        LOGGER(_log.WriteLine("DisableDevice..."));
        SP_PROPCHANGE_PARAMS propChange = { sizeof(SP_CLASSINSTALL_HEADER) };
        propChange.ClassInstallHeader.InstallFunction = DIF_PROPERTYCHANGE;
        propChange.Scope = DICS_FLAG_GLOBAL;
        propChange.StateChange = DICS_DISABLE;
        LOGGER(_log.WriteLine("SetupDiSetClassInstallParams..."));
        if(!SetupDiSetClassInstallParams(hDevInfo, pDevData, (SP_CLASSINSTALL_HEADER*)&propChange, sizeof(propChange)))
            return false;
        LOGGER(_log.WriteLine("SetupDiCallClassInstaller..."));
        if(!SetupDiCallClassInstaller(DIF_PROPERTYCHANGE, hDevInfo, pDevData))
            return false;

        CharConverter cvt;
        // 修改注册表中的 COM 号
        TCHAR devID[MAX_DEVICE_ID_LEN];
        SP_DEVINFO_LIST_DETAIL_DATA devInfoListDetail;
        devInfoListDetail.cbSize = sizeof(devInfoListDetail);
        LOGGER(_log.WriteLine("SetupDiGetDeviceInfoListDetail..."));
        if(!SetupDiGetDeviceInfoListDetail(hDevInfo, &devInfoListDetail))
            return false;
        LOGGER(_log.WriteLine("CM_Get_Device_ID_Ex..."));
        if(CM_Get_Device_ID_Ex(pDevData->DevInst, devID, MAX_DEVICE_ID_LEN, 0, devInfoListDetail.RemoteMachineHandle) != CR_SUCCESS)
            return false;
        const char* pId = cvt.to_char(devID);
        LOGGER(_log << "DevID:<" << pId << ">\n");
        LOGGER(_log.WriteLine("ResetComRegCurrentSetEnum..."));
        if(!_ResetComRegCurrentSetEnum(pId, newName))
            return false;

        // 设置FriendlyName
        LOGGER(_log.WriteLine("SetupDiSetDeviceRegistryProperty..."));
        if(!SetupDiSetDeviceRegistryProperty(hDevInfo, pDevData, SPDRP_FRIENDLYNAME, (PBYTE)cvt.to_char_t(displayName.GetString()), displayName.GetLength()))
            return false;

        // 启用设备
        LOGGER(_log.WriteLine("EnableDevice..."));
        propChange.StateChange = DICS_ENABLE;
        LOGGER(_log.WriteLine("SetupDiSetClassInstallParams..."));
        if(!SetupDiSetClassInstallParams(hDevInfo, pDevData, (SP_CLASSINSTALL_HEADER*)&propChange, sizeof(propChange)))
            return false;
        LOGGER(_log.WriteLine("SetupDiCallClassInstaller..."));
        if(!SetupDiCallClassInstaller(DIF_PROPERTYCHANGE, hDevInfo, pDevData))
            return false;

        LOGGER(_log.WriteLine("ResetComRegSerialCOMM..."));
        // 修改注册表中的 COM 号
        LOGGER(_log << "REG : " << oldName.GetString() << " -> " << newName.GetString() << endl);
        return _ResetComRegSerialCOMM(oldName, newName);
    }
    static uint _GetDbVal(const ByteArray& dbVal, uint port)
    {
        // 被占用
        if(port < 1)
            return 1;

        port -= 1;
        size_t index = port / BIT_OFFSET;
        return BitConvert::Get(dbVal[index], port);
    }
    static void _SetDbVal(ByteArray& dbVal, uint port, uint val)
    {
        if(port < 1)
            return;
        port -= 1;
        size_t index = port / BIT_OFFSET;
        BitConvert::Set(dbVal[index], port, val);
    }
    /// 修改串口
    static size_t _ModifyCOM(const list_t<ModifyComTAG>& modifyCom, ByteArray& dbVal, LoggerAdapter* plog = NULL)
    {
        LOGGER(LoggerAdapter _log;
        if(plog != NULL) _log.Select(*plog));

        const GUID comGUID = COM_GUID_VALUE;
        HDEVINFO hDevInfo = INVALID_HANDLE_VALUE;
        size_t count = 0;
        hDevInfo = SetupDiGetClassDevs(&comGUID,
            NULL,
            NULL,
            DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
        if(hDevInfo == INVALID_HANDLE_VALUE)
            return count;

        // 按照已构建的顺序进行修改
        list_t<ModifyComTAG>::const_iterator itr;
        list_t<DWORD> scanCom;
        for(itr = modifyCom.begin(); itr != modifyCom.end(); ++itr)
        {
            for(DWORD i = 0;; ++i)
            {
                if(list_helper<DWORD>::contains(scanCom, i))
                    continue;

                SP_DEVINFO_DATA devdata = { sizeof(SP_DEVINFO_DATA) };
                if(!SetupDiEnumDeviceInfo(hDevInfo, i, &devdata))
                    break;

                TCHAR fname[256] = { 0 };
                BOOL bSuccess = SetupDiGetDeviceRegistryProperty(
                    hDevInfo, &devdata, SPDRP_FRIENDLYNAME, NULL,
                    (PBYTE)fname, sizeof(fname), NULL);

                if(!bSuccess)
                    break;

                CharConverter cvt;
                ByteBuilder displayName(32);
                displayName = cvt.to_char(fname);
                LOGGER(_log << "Old DisplayName:<" << displayName.GetString() << ">\n");

                ByteBuilder comName = StringConvert::Middle(displayName, '(', ')');
                LOGGER(_log << "COM:<" << comName.GetString() << ">\n");
                uint comPort = ArgConvert::FromString<uint>(comName.GetString() + 3);

                if(itr->OldPort == comPort)
                {
                    if(itr->NewName.length() < 1)
                    {
                        displayName.RemoveTail(comName.GetLength() + 2);
                    }
                    else
                    {
                        displayName.Clear();
                        displayName += itr->NewName.c_str();
                    }
                    ByteBuilder newComName(4);
                    newComName = "COM";
                    newComName += ArgConvert::ToString(itr->NewPort).c_str();

                    displayName += "(";
                    displayName += newComName;
                    displayName += ")";

                    LOGGER(_log << "New DisplayName:<" << displayName.GetString() << ">\n");

                    _SetDbVal(dbVal, comPort, 0);
                    LOGGER(_log << "OldVal[" << comPort << "]=0\n");
                    _SetDbVal(dbVal, itr->NewPort, 1);
                    LOGGER(_log << "NewVal[" << itr->NewPort << "]=1\n");

                    if(_SetComProperty(hDevInfo, &devdata, displayName, comName, newComName, plog))
                    {
                        ++count;
                        scanCom.push_back(i);
                    }
                }
            }
        }

        SetupDiDestroyDeviceInfoList(hDevInfo);
        return count;
    }
    //----------------------------------------------------- 
public:
    //----------------------------------------------------- 
    /// 重置当前串口的占用状态
    /**
     * @brief 重置当前串口的占用状态
     * @date 2016-12-11 10:13
     * 
     * @param [in] comlist 处于链表中的串口号将会被标记为占用状态
     */
    static bool ResetComDB(const list_t<uint>& comlist)
    {
        list_t<uint>::const_iterator itr;
        RegistryKey key = RegistryKey::OpenKey("HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\COM Name Arbiter", KEY_WRITE);
        if(!key.IsValid())
            return false;
        // 直接删除所有键值
        const char comDBKey[] = "ComDB";
        RegValue dbVal;
        dbVal.Value.Append(0x00, 256 / BIT_OFFSET);
        dbVal.dwType = REG_BINARY;
        for(itr = comlist.begin(); itr != comlist.end(); ++itr)
        {
            if((*itr) < 1)
                continue;

            size_t port = (*itr) - 1;
            size_t index = port / BIT_OFFSET;

            BitConvert::Set(dbVal.Value[index], port, 1);
        }
        return key.SetValue(comDBKey, dbVal);
    }
    /// 获取串口号
    static size_t EnumComDB(list_t<uint>& comlist)
    {
        const GUID comGUID = COM_GUID_VALUE;
        HDEVINFO hDevInfo = INVALID_HANDLE_VALUE;
        size_t count = 0;
        hDevInfo = SetupDiGetClassDevs(&comGUID,
            NULL,
            NULL,
            DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
        if(hDevInfo == INVALID_HANDLE_VALUE)
            return count;

        for(DWORD i = 0;;++i)
        {
            SP_DEVINFO_DATA devdata = { sizeof(SP_DEVINFO_DATA) };
            if(!SetupDiEnumDeviceInfo(hDevInfo, i, &devdata))
                break;

            TCHAR fname[256] = { 0 };
            BOOL bSuccess = SetupDiGetDeviceRegistryProperty(
                hDevInfo, &devdata, SPDRP_FRIENDLYNAME, NULL,
                (PBYTE)fname, sizeof(fname), NULL);

            if(!bSuccess)
                break;

            CharConverter cvt;
            ByteBuilder displayName(32);
            displayName = cvt.to_char(fname);
            ByteBuilder comName = StringConvert::Middle(displayName, '(', ')');
            if(comName.GetLength() > 3 && StringConvert::StartWith(comName, "COM", true))
            {
                comlist.push_back(ArgConvert::FromString<uint>(comName.GetString() + 3));
                ++count;
            }
        }
        SetupDiDestroyDeviceInfoList(hDevInfo);
        return count;
    }
    /**
     * @brief 同时修改多个串口号
     * @date 2016-12-10 21:09
     * 
     * @param [in] modifyCom 需要修改的串口号对应关系
     * @param [out] pModifyCom [default:NULL] 修改过的串口号对应关系
     * @param [out] plog [default:NULL] 日志
     */
    static bool ModifyComDB(const list_t<ModifyComTAG>& modifyCom, 
        list_t<ModifyComTAG>* pModifyCom = NULL, LoggerAdapter* plog = NULL)
    {
        // 重新计算需要修改的串口路径
        LOGGER(LoggerAdapter _log;
        if(plog != NULL)
        {
            _log.Select(*plog);
        });
        LOG_FUNC_NAME();

        // 不需要修改的串口号列表
        LOGGER(list_t<uint> oldCom);
        // 配置后的串口号列表
        list_t<uint> newCom;
        // 当前已存在的串口号
        list_t<uint> currentCom;
        list_t<uint>::iterator currItr;

        uint tmpPort = 0;

        list_t<ModifyComTAG> changedCom;
        list_t<ModifyComTAG>::iterator tagItr;

        // 枚举当前已经存在的端口
        EnumComDB(currentCom);
        LOGGER(oldCom = currentCom);
        LOGGER(_log.WriteLine("EnumComDB:");
        for(currItr = currentCom.begin(); currItr != currentCom.end(); ++currItr)
        {
            _log << "COM" << (*currItr) << ' ';
        }
        _log.WriteLine());

        // 初始化链表
        {
            list_t<ModifyComTAG>::const_iterator itr;
            for(itr = modifyCom.begin(); itr != modifyCom.end(); ++itr)
            {
                LOGGER(_log << "COM" << _left_width(itr->OldPort, 3) 
                    << " => COM" << _left_width(itr->NewPort, 3) << " [" << itr->NewName << "]\n");

                // 设置的原始串口不存在
                if(!list_helper<uint>::contains(currentCom, itr->OldPort))
                {
                    LOGGER(_log << "IgnoreError:<COM" << (itr->OldPort) << ">\n");
                    continue;
                }

                // 不允许多个串口最后设置为一个重复的串口,只保留第一个设置有效
                if(itr->OldPort == itr->NewPort || 
                    list_helper<uint>::contains(newCom, itr->NewPort))
                {
                    LOGGER(_log << "IgnoreModify:<COM" << (itr->OldPort) << ">\n");
                    continue;
                }

                tagItr = changedCom.push_back();
                tagItr->OldPort = itr->OldPort;
                tagItr->NewPort = itr->NewPort;
                tagItr->NewName = itr->NewName;

                LOGGER(list_helper<uint>::remove(oldCom, itr->OldPort));
                newCom.push_back(itr->NewPort);
            }
        }
        if(changedCom.size() < 1)
        {
            LOGGER(_log.WriteLine("没有需要修改的端口"));
            return true;
        }

        // 获取串口占用情况
        RegistryKey regkey = RegistryKey::OpenKey("HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\COM Name Arbiter", KEY_WRITE);
        const char comDBKey[] = "ComDB";
        RegValue dbVal;
        dbVal.dwType = REG_BINARY;
        if(!regkey.IsValid() || !regkey.GetValue(comDBKey, dbVal))
        {
            dbVal.Value.Append(0x00, 256 / BIT_OFFSET);
        }
        for(currItr = currentCom.begin(); currItr != currentCom.end(); ++currItr)
        {
            _SetDbVal(dbVal.Value, *currItr, 1);
        }
        bool bWREG = regkey.SetValue(comDBKey, dbVal);
        LOGGER(_log << "REG SetValue:<" << bWREG << ">\n");
        LOGGER(_log.WriteLine("ComDB:");
        for(size_t i = 0;i < dbVal.Value.GetLength();)
        {
            _log << _bit(dbVal.Value[i++]) << ' ';
            if(i % 4 == 0)
                _log << endl;
        };_log.WriteLine());

        const size_t MAX_COMPORT = 256;
        // 建立新的串口端口号表
        for(tagItr = changedCom.begin();tagItr != changedCom.end(); ++tagItr)
        {
            uint currentPort = tagItr->OldPort;
            uint modifyPort = tagItr->NewPort;
            bool isConflict = list_helper<uint>::contains(currentCom, modifyPort);
            // 待修改的串口有个当前串口重叠,需要将当前冲突的串口修改为其他串口
            if(!isConflict)
                continue;
            
            for(uint p = 3; p < MAX_COMPORT; ++p)
            {
                // 当前已经有串口
                if(_GetDbVal(dbVal.Value, p) == 1)
                    continue;
                if(list_helper<uint>::contains(currentCom, p))
                    continue;
                if(list_helper<uint>::contains(newCom, p))
                    continue;

                list_t<ModifyComTAG>::iterator i = changedCom.insert_front(tagItr, ModifyComTAG());
                i->OldPort = modifyPort;
                i->NewPort = p;
                i->NewName = "";
                ++i;

                LOGGER(_log << "Modify: COM" << _left_width(modifyPort, 3) << " => COM" << _left_width(p, 3) << endl);
                // 标记当前状态下的端口列表
                currentCom.push_back(p);
                list_helper<uint>::remove(currentCom, currentPort);
                LOGGER(list_helper<uint>::remove(oldCom, modifyPort));

                for(;i != changedCom.end(); ++i)
                {
                    if(i->OldPort == modifyPort)
                        i->OldPort = p;
                }
                break;
            }
        }
        LOGGER(for(currItr = oldCom.begin();currItr != oldCom.end(); ++currItr)
        {
            string str = "COM";
            str += _left_width(*currItr, 3);
            _log << str << " -> " << str << endl;
        };
        for(tagItr = changedCom.begin();tagItr != changedCom.end(); ++tagItr)
        {
            _log << "COM" << _left_width(tagItr->OldPort, 3) << " => COM" 
                << _left_width(tagItr->NewPort, 3) << endl;
        });

        LOGGER(_log.WriteLine("Begin Modify ComDB:");
        for(size_t i = 0; i < dbVal.Value.GetLength();)
        {
            _log << _bit(dbVal.Value[i++]) << ' ';
            if(i % 4 == 0)
                _log << endl;
        }; _log.WriteLine());
        // 开始重新设置串口
        _ModifyCOM(changedCom, dbVal.Value, plog);

        // 修改注册表中的占用情况
        LOGGER(_log.WriteLine("After ComDB:");
        for(size_t i = 0; i < dbVal.Value.GetLength();)
        {
            _log << _bit(dbVal.Value[i++]) << ' ';
            if(i % 4 == 0)
                _log << endl;
        }; _log.WriteLine());
        bWREG = regkey.SetValue(comDBKey, dbVal);
        LOGGER(_log << "REG SetValue:<" << bWREG << ">\n");
        // 最终修改的串口
        if(pModifyCom != NULL)
        {
            (*pModifyCom) += changedCom;
        }
        return true;
    }
    //----------------------------------------------------- 
};
//--------------------------------------------------------- 
} // namespace ability
} // namespace extension
} // namespace zhou_yb
//--------------------------------------------------------- 
#endif // _LIBZHOUYB_WINCOMINSTALLER_H_
//========================================================= 